package com.pocket.insight.base.sql.dataset.resource;

import com.pocket.insight.base.context.SpringContext;
import com.pocket.insight.base.exception.BizException;
import com.pocket.insight.base.mql.Relation;
import com.pocket.insight.base.mql.relation.StatementRelation;
import com.pocket.insight.base.mql.relation.UnionRelation;
import com.pocket.insight.base.sql.DatasetSchemaProvider;
import com.pocket.insight.base.sql.Dialect;
import com.pocket.insight.base.sql.SchemaProvider;
import com.pocket.insight.base.sql.context.SpecifiedVendorContext;
import com.pocket.insight.base.mql.relation.PrimeRelation;
import com.pocket.insight.base.sql.dataset.DatasetProxy;
import com.pocket.insight.base.sql.dataset.DatasetResource;
import com.pocket.insight.base.sql.dataset.ResourceCreator;
import com.pocket.insight.base.sql.dataset.RuntimeData;
import com.pocket.insight.base.sql.dialect.VirtualDialect;
import com.pocket.insight.base.sql.vendor.BaseVendor;
import com.pocket.insight.controller.dto.DatasetResultDto;
import com.pocket.insight.controller.dto.SqlParam;
import com.pocket.insight.entity.Dataset;
import com.pocket.insight.entity.ME;
import com.pocket.insight.service.ConnectionService;
import com.pocket.insight.service.DatasetService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 描述：
 *
 * @author shuize
 * @create 2024/12/4
 */
@Slf4j
@Data
public abstract class BaseResourceCreator implements ResourceCreator {

    protected static ConnectionService connectionService = SpringContext.getBean(ConnectionService.class);
    protected static DatasetService    datasetService    = SpringContext.getBean(DatasetService.class);
    protected        DatasetResource   datasetResource   = new DatasetResource();
    private          Long              effectiveStorageConnectionId;
    public           Boolean           forceNativeForSub;
    private          List<Object>      userExcludeColumns;
    protected final  RuntimeData       runtimeData;


    protected BaseResourceCreator(RuntimeData runtimeData) {
        this.runtimeData = runtimeData;
    }

    public void init() {
        log.debug("初始化连接【{}】下的【{}】数据集", datasetResource.getDataset().getOptions().getConnectionId(), datasetResource.getDataset().getOptions().getSchema());
        this.datasetResource.setVendor(createVendor());
        this.datasetResource.setEffectiveStorageConnectionId(getEffectiveStorageConnectionId());
        this.datasetResource.setDatasetRelation(createDatasetRelation());
        if (this.datasetResource.getUserExcludeColumns() == null) {
            // 获取当前用户权限内的数据集字段
        }
        log.debug("app {} dataset {} title {} table {} ConnId {} effectiveStorageConnId {} class {}", new Object[]{
                getDatasetResource().getDataset().getAppId(),
                getDatasetResource().getDataset().getId(),
                getDatasetResource().getDataset().getTitle(),
                getDatasetResource().getDataset().getOptions().getTable(),
                getDatasetResource().getDataset().getOptions().getConnectionId(),
                getEffectiveStorageConnectionId(),
                getClass().getCanonicalName()});
    }

    public Long getEffectiveStorageConnectionId() {
        return this.effectiveStorageConnectionId;
    }

    public BaseResourceCreator setEffectiveStorageConnectionId(Long effectiveStorageConnectionId) {
        this.effectiveStorageConnectionId = effectiveStorageConnectionId;
        return this;
    }

    public BaseResourceCreator setDatasetForceNativeForSub(Boolean forceNativeForSub) {
        this.forceNativeForSub = forceNativeForSub;
        return this;
    }


    public Relation createDatasetRelation() {
        log.debug("创建数据集关联关系");
        Long appId = this.datasetResource.getDataset().getAppId();


        //
        if (!(BooleanUtils.isTrue(this.datasetResource.getDataset().getCanPushToMemory()) && CollectionUtils.isNotEmpty(getDatasetResource().getDataset().getSchema()))) {
            return createDatasetRelationImpl();
        }

        Dataset sourceDataset = getDatasetResource().getDataset().partialDeepCopy();
        sourceDataset.setCanPushToMemory(null);
        String backupVendorDesc = SpecifiedVendorContext.getVendorDesc();
        SpecifiedVendorContext.setVendorDesc(null);
        DatasetProxy sourceProxy = DatasetProxy.create(sourceDataset);
        DatasetResultDto resultDto = sourceProxy.fetchFieldsData(sourceProxy.getDatasetResource().getSchemaProvider().getFields().stream().filter(e -> !e.isUserAdded()).collect(Collectors.toList()), new SqlParam());
        SpecifiedVendorContext.setVendorDesc(backupVendorDesc);
        if (resultDto.fetchData().size() > 500) {
            throw new BizException("ERROR_CANNOT_PULL_DATASET_INTO_MEMORY：" + 500);
        }
        VirtualDialect virtualDialect = new VirtualDialect();
        UnionRelation unionRelation = new UnionRelation(virtualDialect);
        if (!resultDto.fetchData().isEmpty()) {
            unionRelation.subRelations.addAll(resultDto.fetchData().stream().map(data -> {
                StatementRelation statementRelation = Relation.statement(virtualDialect);
                for (int i = 0; i < data.size(); i++) {
                    statementRelation.project(ME.create(ME.Kind.constant, BaseVendor.transferDataToSql(data.get(i), sourceDataset.getSchema().get(i)), null, (resultDto.getSchema().get(i)).getFieldName()).setType((sourceDataset.getSchema().get(i)).getType()));
                }
                return statementRelation;
            }).collect(Collectors.toList()));
        } else {
            StatementRelation statementRelation = Relation.statement((Dialect) virtualDialect);
            getDatasetResource().getSchemaProvider().fetchSchemaMap().forEach(
                    (name, field) -> statementRelation.project(ME.create(ME.Kind.constant, null, null, field.getFieldName()).setType(field.getType()))
            );
            unionRelation.add(statementRelation);
        }
        Map<String, Dataset.Field> schemaMap = new LinkedHashMap<>();
        if (getDatasetResource().getSchemaProvider().fetchSchemaMap() != null) {
            getDatasetResource().getSchemaProvider().fetchSchemaMap().forEach((name, field) -> {
                if (field.getDbFieldName() != null) {
                    field.setDbFieldName(null);
                }
                field.getGeneratedOptions().setOriginType(field.fetchType());
                if (field.fetchType() != null) {
                    field.getGeneratedOptions().setNativeType(field.fetchType().toString());
                }
                field.getGeneratedOptions().setSqlType(null);
                field.setDbFieldName(null);
                schemaMap.put(field.getFieldName(), field);
                if (getDatasetResource().getDataset().fetchSchemaMap().containsKey(field.getFieldName())) {
                    getDatasetResource().getDataset().fetchSchemaMap().put(field.getFieldName(), field);
                }
            });
        }
        return PrimeRelation.create(virtualDialect, ME.create(ME.Kind.dataset, unionRelation), new DatasetSchemaProvider(schemaMap, getDatasetResource().getSchemaProvider().fetchMetricMap())).setAlias(getDatasetResource().getTableAlias());
    }


    private Relation createDatasetRelationImpl() {
        Relation relation = this.createDatasetRelationWithoutFilters();
        Pair<List<Object>, List<Object>> innerFiltersPair = this.getInnerFilters();
        Pair<List<Object>, List<Object>> userFiltersPair = this.getUserFilters();
        List<Object> innerFilters = innerFiltersPair == null ? null : innerFiltersPair.getLeft();
        List<Object> userFilters = userFiltersPair == null ? null : userFiltersPair.getLeft();
        List<Object> innerExcludeColumns = innerFiltersPair == null ? null : innerFiltersPair.getRight();
        List<Object> userExcludeColumns = userFiltersPair == null ? null : userFiltersPair.getRight();
        if (userExcludeColumns != null) {
            this.datasetResource.setUserExcludeColumns(userExcludeColumns);
        }

        Function<Boolean, SchemaProvider> getSchemaProvider = (innerOnly) -> {
            SchemaProvider schemaProvider = this.datasetResource.getSchemaProvider();
            Map<String, Dataset.Field> fieldMap = this.getDatasetResource().getSchemaProvider().fetchSchemaMap();
            if (CollectionUtils.isNotEmpty(innerExcludeColumns) || !innerOnly && CollectionUtils.isNotEmpty(userExcludeColumns)) {
                if (CollectionUtils.isNotEmpty(innerExcludeColumns)) {
                    // TODO 内置排除字段
                }
                if (!innerOnly && CollectionUtils.isNotEmpty(userExcludeColumns)) {
                    // TODO 用户排除字段
                }
                schemaProvider = new DatasetSchemaProvider(fieldMap, this.datasetResource.getDataset().fetchMetricMap());
            }
            return (SchemaProvider) schemaProvider;
        };
        StatementRelation statementRelation;
        if (CollectionUtils.isNotEmpty(innerFilters)) {
            if (relation instanceof StatementRelation) {
                ((StatementRelation) relation).where(ME.from(innerFilters));
            } else {
                statementRelation = Relation.statement((relation).getDialect());
                if (!this.datasetResource.getTableAlias().equals((relation).getAlias())) {
                    (relation).setAlias(this.datasetResource.getTableAlias());
                }
                if (relation instanceof PrimeRelation) {
                    Map<String, Dataset.Field> fieldMap = new HashMap();
                    this.getDatasetResource().getSchema().forEach((f) -> {
                        if (!f.isUserAdded()) {
                            Dataset.Field field = new Dataset.Field();
                            field.setFieldName(f.fetchDbFieldName());
                            field.setType(f.getGeneratedOptions().getOriginType());
                            field.getGeneratedOptions().setOriginType(f.getGeneratedOptions().getOriginType());
                            field.getGeneratedOptions().setNativeType(f.getGeneratedOptions().getNativeType());
                            field.getGeneratedOptions().setSqlType(f.getGeneratedOptions().getSqlType());
                            field.setDatasetId(this.getDatasetResource().getDataset().getId());
                            fieldMap.put(field.getFieldName(), field);
                        }

                    });
                    if (MapUtils.isNotEmpty(fieldMap)) {
                        ((PrimeRelation) relation).setSchemaProvider(new DatasetSchemaProvider(fieldMap, null));
                    }
                }

                statementRelation.scan(relation);
                statementRelation.where(ME.from(innerFilters));
                relation = statementRelation;
            }
        }

        if (CollectionUtils.isNotEmpty(userFilters)) {
            statementRelation = Relation.statement((relation).getDialect()).alias(this.getDatasetResource().getTableAlias());
            if (relation instanceof PrimeRelation) {
                ((PrimeRelation) relation).setSchemaProvider(getSchemaProvider.apply(true));
                (relation).setAlias(this.getDatasetResource().getTableAlias());
                statementRelation.scan(relation);
            } else {
                Relation primeRelation = PrimeRelation.create((relation).getDialect(), ME.create(ME.Kind.dataset, relation), getSchemaProvider.apply(true)).setAlias(this.getDatasetResource().getTableAlias());
                statementRelation.scan(primeRelation);
            }

            statementRelation.where(ME.from(userFilters));
            SchemaProvider schemaProvider = getSchemaProvider.apply(false);
            if (schemaProvider != null && (schemaProvider).fetchSchemaMap() != null) {
                Map<String, Dataset.Field> fieldsMap = new LinkedHashMap();

                for (Map.Entry<String, Dataset.Field> entry : schemaProvider.fetchSchemaMap().entrySet()) {
                    statementRelation.project(ME.create(ME.Kind.field, entry.getKey()).setUid(entry.getKey()));
                    fieldsMap.put(entry.getKey(), Dataset.Field.fromDatasetFieldKeepTypeOnly(entry.getValue()));
                }
                schemaProvider = new DatasetSchemaProvider(fieldsMap, (schemaProvider).fetchMetricMap());
            }

            return PrimeRelation.create((relation).getDialect(), ME.create(ME.Kind.dataset, statementRelation), schemaProvider).setAlias(this.getDatasetResource().getTableAlias());
        } else if (relation instanceof PrimeRelation) {
            PrimeRelation primeRelation = (PrimeRelation) relation;
            primeRelation.setSchemaProvider(getSchemaProvider.apply(false));
            primeRelation.setAlias(this.datasetResource.getTableAlias());
            return primeRelation;
        } else {
            return PrimeRelation.create((relation).getDialect(), ME.create(ME.Kind.dataset, relation), getSchemaProvider.apply(false)).setAlias(this.getDatasetResource().getTableAlias());
        }
    }

    protected abstract Relation createDatasetRelationWithoutFilters();

    Pair<List<Object>, List<Object>> getInnerFilters() {
        return Pair.of(new ArrayList<>(), new ArrayList<>());
    }

    protected Pair<List<Object>, List<Object>> getUserFilters() {
        return Pair.of(new ArrayList<>(), new ArrayList<>());
    }

}
